{ "cells": [ { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "# Mixed Mode S-Parameter Conversion\n", "\n", "When analyzing differential devices, mixed-mode S-Parameters are typically used to look at differential and common mode\n", "characteristics. Scikit-rf provides functions to ease conversion between single ended and mixed mode S-parameters.\n", "Nevertheless, the user must be careful to set up ports correctly and take note of the form of the mixed-mode matrix to\n", "prevent confusion. This notebook will introduce you to the process of converting from single ended to mixed mode S\n", "parameters using a [50 ohm calibration load](https://www.formfactor.com/download/iss-map-129-246/) as an example.\n", "The experimental port setup consists of two differential probes as shown:\n", "\n", "\n", "\n", "Experimental data for the 50 ohm load taken in normal \"single ended\" mode will be compared against data saved in\n", "a mixed mode format and taken using Keysight's integrated True Mode Stimulus, which applies true differential\n", "and common mode signals during the measurement process." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "import re\n", "\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "\n", "import skrf as rf\n", "\n", "sedatafile = r'mixedmodebasics_files/load_se.s4p'\n", "mmdatafile = r'mixedmodebasics_files/load_truemode_balbal.s4p'\n", "\n", "for file in [sedatafile, mmdatafile]:\n", " with open(file, encoding='cp1252') as f:\n", " for line in f:\n", " print(line.rstrip())\n", " if re.search('#', line):\n", " break\n", " else:\n", " pass\n", " print('\\n')" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "The header data for the mixed mode data indicates that it is saved in the following format:\n", "\n", "$$\n", "\\begin{bmatrix}\n", "\\begin{bmatrix}\n", " S_{dd} & S_{dc} \\\\\n", " S_{cd} & S_{cc}\n", "\\end{bmatrix}_{11} &\n", "\\begin{bmatrix}\n", " S_{dd} & S_{dc} \\\\\n", " S_{cd} & S_{cc}\n", "\\end{bmatrix}_{12} \\\\\n", "\\begin{bmatrix}\n", " S_{dd} & S_{dc} \\\\\n", " S_{cd} & S_{cc}\n", "\\end{bmatrix}_{21} &\n", "\\begin{bmatrix}\n", " S_{dd} & S_{dc} \\\\\n", " S_{cd} & S_{cc}\n", "\\end{bmatrix}_{22}\n", "\\end{bmatrix}\n", "$$\n", "\n", "It is important to keep this in mind, as this format may vary between different software and hardware.\n", "For instance, skrf will transform singled ended data to the following form when two balanced ports are present:\n", "\n", "$$\n", "\\begin{bmatrix}\n", "\\begin{bmatrix}\n", " S_{11} & S_{12} \\\\\n", " S_{21} & S_{22}\n", "\\end{bmatrix}_{dd} &\n", "\\begin{bmatrix}\n", " S_{11} & S_{12} \\\\\n", " S_{21} & S_{22}\n", "\\end{bmatrix}_{dc} \\\\\n", "\\begin{bmatrix}\n", " S_{11} & S_{12} \\\\\n", " S_{21} & S_{22}\n", "\\end{bmatrix}_{cd} &\n", "\\begin{bmatrix}\n", " S_{11} & S_{12} \\\\\n", " S_{21} & S_{22}\n", "\\end{bmatrix}_{cc}\n", "\\end{bmatrix}\n", "$$\n", "\n", "To transform our single ended data, we must first pair the ports as they existed during the experimental setup with\n", "ports 1 and 3 making up one balanced port, and 2 and 4 on the other probe. We can then use the `se2gmm()` method of the\n", "skrf.Network class to transform to a mixed mode s-parameter matrix, with the `p` parameter used to specify the number of\n", "mixed mode ports. Skrf will transform the ports in pairs starting at the lowest number ports (1 and 3 after our\n", "renumbering) and continue until the matrix contains `p` mixed mode ports, leaving the remaining ports as single ended." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "sedata = rf.Network(sedatafile)\n", "sedata.renumber([0, 1, 2, 3], [0, 2, 1, 3]) # pair ports as 1,3 and 2,4 to match experimental setup\n", "sedata.se2gmm(p=2) # two balanced ports\n", "# sedata now in form Sdd Sdc with each submatrix as S11 S12\n", "# Scd Scc S21 S22" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "The following function converts a two-port balanced-balanced network in the skrf format into the format used by\n", "Keysight to make data comparisons easier:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "def gmm_reorder(m):\n", " \"\"\"\n", " Reorders data from form 11 12 with each submatrix as dd dc\n", " 21 22 cd cc\n", " to form dd dc with each submatrix as 11 12\n", " cd cc 21 22\n", " \"\"\"\n", " b = np.array([1, 0, 0, 0,\n", " 0, 0, 1, 0,\n", " 0, 1, 0, 0,\n", " 0, 0, 0, 1]).reshape(4, 4)\n", " m = b.dot(m.dot(b))\n", " return m\n", "\n", "mmdata = rf.Network(mmdatafile)\n", "# raw data is mmdata in form S11 S12 with each submatrix as dd dc\n", "# S21 S22 cd cc\n", "for i in range(mmdata.frequency.npoints):\n", " mmdata.s[i, :, :] = gmm_reorder(mmdata.s[i, :, :])\n", "mmdata.port_modes = ['D', 'D', 'C', 'C']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that the two networks are in the same format, we can see that skrf does not consider them equal.\n", "This is because the networks are not identical, they are merely two measurements of the same device, both with\n", "experimental noise and variation. \n", "If the tolerances of the comparison are relaxed, the comparison of the networks returns True:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "print(sedata == mmdata) # uses np.allclose with tight tolerances\n", "print(np.allclose(abs(sedata.s), abs(mmdata.s), rtol=1, atol=1e-3)) # relaxed tolerances" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Plotted, the error looks like this:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" }, "scrolled": false }, "outputs": [], "source": [ "for m in range(4):\n", " for n in range(4):\n", " plt.plot(sedata.f, abs(mmdata.s)[:,m,n]-abs(sedata.s)[:,m,n], label=f'S{sedata._fmt_trace_name(m, n)}')\n", " plt.title('Magnitude Error between Measurements')\n", " plt.legend(bbox_to_anchor=(1.1, 1.05))\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, axes = plt.subplots(2,2, sharex=True, sharey='row', figsize=(14,6))\n", "sedata.plot_s_db(ax=axes[0][0])\n", "mmdata.plot_s_db(ax=axes[0][1])\n", "sedata.plot_s_deg(ax=axes[1][0])\n", "mmdata.plot_s_deg(ax=axes[1][1])\n", "axes[0][0].get_legend().remove()\n", "axes[1][0].get_legend().remove()\n", "axes[1][1].get_legend().remove()\n", "axes[0][1].get_legend().remove()\n", "axes[0][0].set_title('sedata Magnitude')\n", "axes[0][1].set_title('mmdata Magnitude')\n", "axes[1][0].set_title('sedata Phase')\n", "axes[1][1].set_title('mmdata Phase')\n", "\n", "fig.legend(*axes[0, 1].get_legend_handles_labels(), loc=\"center right\")\n", "fig.tight_layout()\n", "plt.subplots_adjust(top=0.9, right=0.8)\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "fig, axes = plt.subplots(4,4, sharex=True, figsize=(14,8))\n", "for m in range(4):\n", " for n in range(4):\n", " sedata.plot_s_deg_unwrap(m=m, n=n, ax=axes[m][n])\n", " mmdata.plot_s_deg_unwrap(m=m, n=n, ax=axes[m][n], ls='--')\n", " axes[m][n].get_legend().remove()\n", " axes[m][n].set_title(f'S{sedata._fmt_trace_name(m, n)}')\n", "fig.tight_layout()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.16" } }, "nbformat": 4, "nbformat_minor": 1 }